#pragma once

#include <string>
#include <vector>
#include <map>
#include <unordered_map>

#include "ResultTable.h"
#include "ResultGraphTable.h"
#include "Table.h"
#include "PKB.h"
#include "QueryTable.h"
#include "QueryProjector.h"
#include "PatternType.h"
#include "Optimizer.h"

typedef enum MatchType;

class QueryEvaluator {
	private:
		static const int MAX_BYTES_FOR_INTEGERSTRING = 33; // Maximum bytes for any integer string

		typedef std::unordered_map<int, bool> IntBoolMap;
		typedef Table<std::string, int> SynonymResultsTable;
		typedef std::unordered_map<std::string, SynonymResultsTable*> SynonymResultsTablePtrMap;

		// Not really used anymore, except for MATCH_BOTH for Uses(_,_) etc.
		enum MatchType {
			MATCH_NONE, MATCH_LEFT, MATCH_RIGHT, MATCH_BOTH
		};

		PKB *mPkb; 
		std::unordered_map<std::string, std::vector<int>> mMapOfSynonymToIntegerResults; // stores the results for each synonym as the query is evaluated
		ResultTable mResultTable;
		ResultGraphTable mResultGraphTable;
		SynonymResultsTablePtrMap mSynonymResultsTables;
		SynonymTable mSynonymTable;
		ConstraintTable mConstraintTable;
		RelationshipClauseTable mRelationshipClauseTable;
		PatternTable mPatternTable;

		// Uses the graph algorithm and Optimizer to evaluate the entire query
		std::vector<std::string>& evaluateQueryUsingResultGraphTableUsingOptimizer();
		// Evaluates all the clauses in the Optimizer with the graph algorithm
		bool evaluateUsingResultGraphTableWithOptimizer(Optimizer *optimizer);
		// Uses the graph algorithm to evaluate one clause
		bool evaluateOneClauseUsingResultGraphTable(const Clause &clause);

		// Uses the table algorithm and Optimizer to evaluate the entire query
		std::vector<std::string>& evaluateQueryUsingTableUsingOptimizer();
		// Evaluates all the clauses in the Optimizer with the table algorithm
		bool evaluateUsingTableWithOptimizer(Optimizer *optimizer);
		// Uses the table algorithm to evaluate one clause
		bool evaluateOneClauseUsingTable(const Clause &clause);

		// Uses the graph algorithm to evaluate the entire query
		std::vector<std::string>& evaluateQueryUsingResultGraphTable();
		// Uses the table algorithm to evaluate the entire query
		std::vector<std::string>& evaluateQueryUsingTable();

		/*-----------------------------------------------------------
		 *	Table-driven functions
		 *-----------------------------------------------------------*/
		// Updates the mSynonymResultsTables with the given good indexes for synonym1
		bool updateTableWithGoodIndexes(std::string synonym1, const IntBoolMap& goodIndexesMap);
		// Updates the mSynonymResultsTables with the given relationship between synonym1 and synonym2,
		// possibly connecting multiple tables if synonym1 and synonym2 are in separate tables
		bool updateTablesWithNewRelationship(std::string synonym1, std::string synonym2, RelationshipType relType);
		//	Copies rows from tableFrom onto this table.
		//	Rows to copy should be specified in rowIndexesToCopyFrom.
		//	Returns the number of rows duplicated in tableTo during the copying process.
		int copyTableRows(Table<std::string, int>& tableTo, int tableToRowToDuplicate, const Table<std::string, int>& tableFrom, 
			const std::vector<int>& rowIndexesToCopyFrom);

		/*-----------------------------------------------------------
		 *	With clause functions
		 *-----------------------------------------------------------*/
		// Evaluates all the with-clauses existing in the mConstraintTable, using the mSynonymResultsTables,
		// returning true if ALL constraints evaluated are true, and false if even one constraint is false.
		bool evaluateAllWithClausesUsingTable();
		// Evaluates one with-clause (ConstraintEntry), using the mSynonymResultsTables,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseUsingTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on both sides, using the mSynonymResultsTables,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereBothArgsAreSynonymsUsingTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on the left side, using the mSynonymResultsTables,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereTheLeftArgIsSynonymUsingTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on the right side, using the mSynonymResultsTables,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereTheRightArgIsSynonymUsingTable(const ConstraintEntry &constraintEntry);

		/*-----------------------------------------------------------
		 *	Pattern clause functions
		 *-----------------------------------------------------------*/
		// Evaluate all pattern clauses using the mSynonymResultsTables
		bool evaluateAllPatternsUsingTable();
		// Evaluates one pattern, using the Table, given a oattern entry as argument.
		// Note: arg1SynType will be INVALID_ARG if arg1 is NOT a synonym.
		bool evaluateOnePatternUsingTable(const PatternEntry& patternEntry);
		// Evaluate one pattern clause using the mSynonymResultsTables
		bool evaluateOnePatternUsingTable(std::string patternSyn, PatternType patternType, 
			std::string arg1, bool perfectMatchRequired1, RelationshipArgumentType arg1SynType,
			TreeNode* arg2, bool perfectMatchRequired2, TreeNode* arg3, bool perfectMatchRequired3);
		// Evaluate one pattern clause where arg1 is a synonym using the mSynonymResultsTable
		bool evaluateOnePatternWhereArg1IsSynonymUsingTable(std::string patternSyn, PatternType patternType, 
			std::string arg1, bool perfectMatchRequired1, RelationshipArgumentType arg1SynType,
			TreeNode* arg2, bool perfectMatchRequired2,	TreeNode* arg3, bool perfectMatchRequired3);
		// Evaluate one pattern clause where arg1 is not a synonym using the mSynonymResultsTables
		bool evaluateOnePatternWhereArg1IsNotSynonymUsingTable(std::string patternSyn, PatternType patternType, 
			std::string arg1, bool perfectMatchRequired1, RelationshipArgumentType arg1SynType,
			TreeNode* arg2, bool perfectMatchRequired2,	TreeNode* arg3, bool perfectMatchRequired3);

		/*-----------------------------------------------------------
		 *	Relationship clause functions
		 *-----------------------------------------------------------*/
		// Evaluates all explicit relationship clauses
		bool evaluateAllRelationshipClausesUsingTable();
		// Evaluate one explicit relationship clause
		bool evaluateOneRelationshipClauseUsingTable(const RelationshipEntry &relationshipEntry);


		// Finds all the variable indexes which are used as control variables for while/if statements of the specified whileOrIfSynonym
		IntBoolMap& getVarIndexesOfControlVariablesForWhileOrIfStatementsUsingTable(std::string whileOrIfSynonym, PatternType patternType,
			TreeNode *arg2, bool perfectMatchRequired2);

		// Finds, using the pkb, and returns ALL the relationship pairs for the relationship relType between synonyms 1 and 2
		std::unordered_map<int, IntBoolMap*>& getAllTrueRelationshipPairs(std::string synonym1, std::string synonym2, RelationshipType relType);
		// Returns the indexes for synonym1 which have at least one true relationship with any of the indexes in relArg2Indexes
		IntBoolMap& getIndexesOfSynonym1ThatHaveTrueRelationship(std::string synonym1, const std::vector<int>& relArg2Indexes, 
			RelationshipType relType);
		// Returns the indexes for synonym2 which have at least one true relationship with any of the indexes in relArg1Indexes
		IntBoolMap& getIndexesOfSynonym2ThatHaveTrueRelationship(const std::vector<int>& relArg1Indexes, std::string synonym2, 
			RelationshipType relType);
		// Removes all the elements in indexes that are not equal to theIndex.
		void leaveOnlyEqualIndexes(IntBoolMap *indexes, int theIndex);
		

		// Evaluates all the with-clauses existing in the mConstraintTable, using the mResultGraphTable,
		// returning true if ALL constraints evaluated are true, and false if even one constraint is false.
		bool evaluateAllWithClausesUsingResultGraphTable();
		// Evaluates one with-clause (ConstraintEntry), using the mResultGraphTable,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseUsingResultGraphTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on both sides, using the mResultGraphTable,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereBothArgsAreSynonymsUsingResultGraphTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on the left side, using the mResultGraphTable,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereTheLeftArgIsSynonymUsingResultGraphTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has a synonym on the right side, using the mResultGraphTable,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereTheRightArgIsSynonymUsingResultGraphTable(const ConstraintEntry &constraintEntry);
		// Evaluates one with-clause (ConstraintEntry) that has no synonyms on either side, using the mResultGraphTable,
		// and returns true if the constraint is possible, and false if not.
		bool evaluateOneWithClauseWhereBothArgsAreNotSynonyms(const ConstraintEntry &constraintEntry);
		// Evaluates all patterns existing in the mPatternTable, using the mResultGraphTable,
		// returning true if ALL patterns evaluated are true, and false if even one pattern is false.
		bool evaluateAllPatternsUsingResultGraphTable();
		// Evaluates one pattern, using the mResultGraphTable, given all the pattern arguments as parameters.
		// Note: arg1SynType will be INVALID_ARG if arg1 is NOT a synonym.
		bool evaluateOnePatternUsingResultGraphTable(const PatternEntry& patternEntry);
		bool evaluateOnePatternWhereArg1IsSynonymUsingResultGraphTable(std::string patternSyn, PatternType patternType, 
								std::string arg1, bool perfectMatchRequired1, RelationshipArgumentType arg1SynType,
								TreeNode* arg2, bool perfectMatchRequired2,
								TreeNode* arg3, bool perfectMatchRequired3);
		bool evaluateOnePatternWhereArg1IsNotSynonymUsingResultGraphTable(std::string patternSyn, PatternType patternType, 
								std::string arg1, bool perfectMatchRequired1, RelationshipArgumentType arg1SynType,
								TreeNode* arg2, bool perfectMatchRequired2,
								TreeNode* arg3, bool perfectMatchRequired3);
		// Evaluates all the relationship clauses from the mRelationshipClauseTable, using the mResultGraphTable,
		// and returns true if all relationship clauses are true.
		// Otherwise returns false if any of the relationship clauses are false.
		bool evaluateAllRelationshipClausesUsingResultGraphTable();
		// Evaluates only one relationship clause at a time, using the mResultGraphTable,
		// and returns true if the relationship is true.
		// Otherwise returns false if the relationship is not true.
		bool evaluateOneRelationshipClauseUsingResultGraphTable(const RelationshipEntry &relationshipEntry);
		// Fetches the appropriate indexes for the given arg1, given the relationship type and the relationship argument type of arg1.
		std::vector<int>& fetchAppropriateArg1Indexes(RelationshipType relType, std::string relArg1, RelationshipArgumentType argType1);
		// Fetches the appropriate indexes for the given arg2, given the relationship type and the relationship argument type of arg2.
		std::vector<int>& fetchAppropriateArg2Indexes(RelationshipType relType, std::string relArg2, RelationshipArgumentType argType2);
		// Fetches the appropriate statement indexes given the arguments 
		// relationship argument relArg and its relationship argument type argType.
		// E.g. if relArg is an integer-parseable string, then just returns one index (treating it as a single stmtIndex)
		// Otherwise, it will look up the PKB to fetch a list of statements based on the argType specified.
		// Note: If there are already answers for the relArg (it is a synonym that has been used in a relationship
		//		query or patter before), then those answers are returned instead.
		std::vector<int>& fetchStmtIndexes(std::string relArg, RelationshipArgumentType argType);
		std::vector<int>& fetchStmtIndexesForNonSynonym(std::string relArg, RelationshipArgumentType argType);
		// Fetches the appropriate procedure indexes given the arguments
		// relationship argument relArg and its relationship argument type argType.
		// E.g. if relArg is a string, then just returns one index (treating it as the name of a procedure)
		// Otherwise, it will look up the PKB to fetch a list of procedures based on the argType specified.
		std::vector<int>& fetchProcIndexesForNonSynonym(std::string relArg, RelationshipArgumentType argType);
		// Fetches the appropriate assign indexes given the arguments
		// relationship argument relArg and its relationship argument type argType.
		// E.g. if relArg is a string, then just returns one index IF it is ASSIGN entity type
		// (treating it as stmt# of an ASSIGN)
		// Otherwise, it will look up the PKB to fetch a list of ASSIGNs based on the argType specified.
		std::vector<int>& QueryEvaluator::fetchAssignIndexesForNonSynonym(std::string relArg, RelationshipArgumentType argType);
		// Fetches the appropriate variable indexes given the arguments 
		// relationship argument relArg and its relationship argument type argType.
		// E.g. if relArg is a string, then just returns one index (treating it as a single variable definition)
		// Otherwise, it will look up the PKB to fetch the entire list of variable indexes.
		// Note: If there are already answers for the relArg (it is a synonym that has been used in a relationship
		//		query or patter before), then those answers are returned instead.
		std::vector<int>& fetchVariableIndexes(std::string relArg, RelationshipArgumentType argType);
		std::vector<int>& fetchVariableIndexesForNonSynonym(std::string relArg, RelationshipArgumentType argType);
		// Finds all the variable indexes which are used as control variables for while/if statements of the specified whileOrIfSynonym
		std::vector<int>& getVarIndexesOfControlVariablesForWhileOrIfStatements(std::string whileOrIfSynonym, PatternType patternType,
																				TreeNode *arg2, bool perfectMatchRequired2);
		// Converts a vector of integers to a vector of its corresponding strings.
		std::vector<string>& convertIntegerVectorToStringVector(const vector<int>& integers);
		// Translates a vector of variable indexes to their variable names,
		// and returns the list of variable names as a vector of strings.
		std::vector<string>& translateIndexesToVariableNames(const std::vector<int>& variableIndexes);
		// Translates a vector of proc indexes to their variable names,
		// and returns the list of variable names as a vector of strings.
		std::vector<string>& translateIndexesToProcNames(const std::vector<int>& procIndexes);
		// Translates a vector of call indexes to their called proc names,
		// and returns the list of proc names as a vector of strings.
		std::vector<string>& translateCallIndexesToCalledProcNames(const std::vector<int> &callIndexes);
		// Sorts the integer vector from smallest to largest.
		void sortIntegerVector(vector<int> *vect);
		// Validates the resultIndexes, ensuring that all the indexes > 0 and has no repetition,
		// and that every index is of the DesignEntityType specified by the argument synonymType.
		// Returns a vector of valid result indexes after removing the invalid indexes.
		std::vector<int>& validateResultIndexes(const vector<int>& resultIndexes, DesignEntityType synonymType);
		// Gets from the PKB all the program line indexes, or all the variable indexes or all the constant integers
		// and returns a vector of integers.
		std::vector<int>& getIndexesOfEntityType(DesignEntityType synEntityType);
		// Gets from the PKB all the program line indexes, or all the variable names or all the constant integers
		// and converts those into a list of strings, one string for each program line index/variable name/integer.
		std::vector<string>& getResultsOfEntityTypeInStrings(DesignEntityType synEntityType);
		// Returns the result indexes as a vector of integers,
		// from the mSynonymResultsTables.
		std::vector<int>& getResultIndexesFromTable(std::string synonym);
		// Returns the results as a vector of strings for the specified synonym of synEntityType.
		// Gets from the mSynonymResultsTables the result indexes, and then converts them to
		// strings given the type of synEntityType.
		// Returns all possible indexes for a synonym if its table does not exist.
		std::vector<string>& getResultStringsFromTable(std::string synonym, ConstraintAttrType synAttrType, DesignEntityType synEntityType);
		// Returns the results as a vector of strings for the specified synonym of synEntityType.
		// Gets from the mResultGraphTable the result indexes, and then converts them to
		// strings given the type of synEntityType.
		std::vector<string>& getResultStringsFromResultGraphTable(std::string synonym, ConstraintAttrType synAttrType, DesignEntityType synEntityType);
		// Returns true if the relationship argument type specified is a synonym type, and false otherwise.
		bool isSynonymType(RelationshipArgumentType argType);
		// Returns true if the relationship of relType is true for (synonym1Value, synonym2Value), false otherwise
		bool checkIfIsTrueRelationship(RelationshipType relType, int synonym1Value, int synonym2Value);
		// Returns true if the non-synonym (almost redundant) relationship is true, and false otherwise
		bool checkIfIsTrueRelationshipForNonSynonyms(RelationshipType relType, std::string relArg1, std::string relArg2, 
													RelationshipArgumentType argType1, RelationshipArgumentType argType2);
		// Returns the indexes for synonym1 which have NO true relationship with any of the indexes in relArg2Indexes
		std::vector<int>& getIndexesOfSynonym1ThatHaveNoTrueRelationship(std::string synonym1, const std::vector<int>& relArg2Indexes, RelationshipType relType);
		// Returns the indexes for synonym2 which have NO true relationship with any of the indexes in relArg1Indexes
		std::vector<int>& getIndexesOfSynonym2ThatHaveNoTrueRelationship(const std::vector<int>& relArg1Indexes, std::string synonym2, RelationshipType relType);
		// Checks the mSynonymTable for the specified synonym.
		// Returns true if it exists, and false otherwise.
		bool isSynonymInSynonymTable(std::string synonym);
		bool relationshipEntryContainsSynonym(const RelationshipEntry& relEntry, std::string synonym);
		bool relationshipEntryContainsAnySynonym(const RelationshipEntry& relEntry);
		
		// Finds, using the pkb, and returns ALL the indexes of synonym2 for the relationship relType between 
		// the specified index synonym1Value of synonym 1 and all of synonym2
		IntBoolMap& getTrueRelationshipSynonym2IndexesForGivenSynonym1Index(RelationshipType relType, 
			std::string synonym1, std::string synonym2, int synonym1Value);
		// Finds, using the pkb, and returns ALL the indexes of synonym1 for the relationship relType between 
		// all of synonym1 and the specified index synonym2Value
		IntBoolMap& getTrueRelationshipSynonym1IndexesForGivenSynonym2Index(RelationshipType relType, 
			std::string synonym1, std::string synonym2, int synonym2Value);
		// Gets a vector of all the indexes of the given synEntityType
		std::vector<int>& getAllIndexes(DesignEntityType synEntityType);
		// Converts the given synonym1Value (an index) to the appropriate indexes of 
		// variables/procedures/call synonyms (specified with synonym2Type)
		std::vector<int>& convertLeftSynonymIndexToRightSynonymIndex_VariableOrProcedureOrCall(int synonym1Value, DesignEntityType synonym1Type, DesignEntityType synonym2Type);
		// Checks, using the pkb, the given indexes vector for indexes which are of the specified synonymEntityType
		// and returns only those validated indexes in a vector
		std::vector<int>& validateIndexes(const std::vector<int>& indexes, DesignEntityType synonymEntityType);
		// Converts the given integer vector into a unordered_map<int, bool>
		IntBoolMap& convertIntVectorToIntBoolMap(const std::vector<int>& intVector);

		// For debugging.
		// Prints the mMapSynonymToIntegerResults.
		void debuggingPrintMap();

		/**-----------------------------------------------------------------------
		 * Result-getting functions, for projection
		 *
		 *------------------------------------------------------------------------*/
		// Gets the result string for projection.
		// If isQueryValid is specified as false, then either an empty string or
		// a "false" will be returned. "false" will be returned BOOLEAN was selected.
		// If isQueryValid is specified as true, then this function searches the ResultGraphTable
		// for the answers. Also uses mResultTable to see what was selected in the query.
		std::vector<string>& getResultsForProjectionUsingResultGraphTable(bool isQueryValid);
		// Gets the result string for projection.
		// If isQueryValid is specified as false, then either an empty string or
		// a "false" will be returned. "false" will be returned BOOLEAN was selected.
		// If isQueryValid is specified as true, then this function searches the Table
		// for the answers. Also uses mResultTable to see what was selected in the query.
		// TODO: does not handle tuples atm.
		std::vector<string>& getResultsForProjectionUsingTable(bool isQueryValid);
		// Gets the tuple result strings by using the mSynonymResultsTables.
		std::vector<string>& getTupleResultStringsUsingTable();
		// Gets the tuple result strings by using the mResultGraphTable.
		std::vector<string>& getTupleResultStringsUsingResultGraphTable();
		// Converts an index to the appropriate string for the given synonym
		std::string convertIndexToString(int index, ConstraintAttrType synAttrType, const std::string& synonym);
		// Converts an integer into a string
		std::string convertIntegerToString(int integer);
		// Takes table2 and for every row r1 in table1, 
		// appends (and duplicates if necessary) every row r2 from table2 onto it. 
		// table1 will be the resultant table of the merge at the end.
		void mergeTwoTupleTables(SynonymResultsTable *table1, const SynonymResultsTable& table2);
	public:
		/**
		 * Builds a QueryEvaluator with the specified PKB to which it will query for the necessary information to perform query evaluation.
		 */
		QueryEvaluator(PKB *pkb);
		~QueryEvaluator(void);
		
		static const std::string TRUE_RESULT;
		static const std::string FALSE_RESULT;

		static const int ERROR_COPYTABLEROWS_ROWTODUPLICATE_DOES_NOT_EXIST;
		static const int ERROR_COPYTABLEROWS_ROWTOCOPYFROM_DOES_NOT_EXIST;
		
		/**
		 * Evaluates the query,
		 * The queryTable argument should contain the 
		 * 4 of its member tables, all already preprocessed and validated.
		 */
		std::vector<string>& evaluateQuery(const QueryTable& queryTable);
};

